#!/bin/bash 
#  
# Script for keeping zfs filesystems in sync on an alternative host
#

# The config file should look like this:
#
#local_zpool remote_zpool filesystem interval remote_host 
#pool1         pool1         dataset-name          10       remote_host
#
# Then schedule the sync's in cron as /path/to/zfs-sync.sh dataset-name
# (Requires ssh keys)

config=/etc/zfs-sync/config

if [ $# -ne 1 ]; then
	echo "Usage: $0 fs-name"
	exit 1
else
	filesystem_arg=$1
fi

poolsource=$(awk '{ if ( $3 == "'${filesystem_arg}'" ) { print $1 } }' $config)
pooldest=$(awk   '{ if ( $3 == "'${filesystem_arg}'" ) { print $2 } }' $config)
filesystem=$(awk '{ if ( $3 == "'${filesystem_arg}'" ) { print $3 } }' $config)
interval=$(awk   '{ if ( $3 == "'${filesystem_arg}'" ) { print $4 } }' $config)
remote=$(awk     '{ if ( $3 == "'${filesystem_arg}'" ) { print $5 } }' $config)
curtime=$(date +%y%m%d%H%M)

pid="/var/run/$filesystem.zfssync"
if [ -f "$pid" ]; then
	echo "echo already running for this fs (found $pid)....bye" 
	exit 1
else
	touch $pid 
fi

if [ "${filesystem}" != "${filesystem_arg}" ]; then
	echo "Error! Config filesystem and argument filesystem do not match!....bye"
	[ -f "$pid" ] && rm -f "$pid"
	exit 1
fi

check() {
	cmd="zfs list -t filesystem | awk '{print \$1}' | egrep \"^$poolsource/$filesystem\$\""
	eval $cmd
	if [ $? -ne 0 ]; then
		echo "Error: zfs filesystem $poolsource/$filesystem does not exist....bye"
		[ -f "$pid" ] && rm -f "$pid"
		exit 1
	fi
	cmd="ssh $remote id"
	eval $cmd
	if [ $? -ne 0 ]; then
		echo "could not execute remote test command: $cmd please fix!...bye"
		[ -f "$pid" ] && rm -f "$pid"
		exit 2
	fi
	cmd="ssh $remote zfs list -t filesystem | awk '{print \$1}' |  egrep \"^$pooldest/$filesystem\$\""
	eval $cmd
	if [ $? -ne 0 ]; then
		echo "Warning: zfs remote filesystem $pooldest/$filesystem does not exist"
	fi
	last_remote_syncsnapshot_exist 1 ${pooldest} ${filesystem} ${remote}
}

create_snapshot() {
	cmd="zfs snapshot $1/$2@sync-$3"
	eval $cmd
	if [ $? -ne 0  ]; then
		echo "Error: snapshot failed... bye" >&2
		[ -f "$pid" ] && rm -f "$pid"
		exit 1
	fi

	last_snapshot 1 $1 $2
	if [ "${last}" == "${curtime}" ]; then
		echo "Snapshot created: OK"
	else
		echo "Snapshot failed: ${last} - ${curtime}!" >&2
		[ -f "$pid" ] && rm -f "$pid"
		exit 1
	fi
}

last_remote_syncsnapshot_exist() {
	cmd="zfs list -H -r -s creation -t snapshot -o name $2/$3|tail -$1|head -1"
	last=$(ssh root@$4 $cmd)
	[ -z "$last" ] && return
	echo $last | grep "@sync"
	if [ ! $? -eq 0 ]; then
		echo "last remote snapshot is not of type @sync.....bye" 
		[ -f "$pid" ] && rm -f "$pid"
		exit 3
	fi
}

last_snapshot() {
	cmd="zfs list -H -r -s creation -t snapshot -o name $2/$3|tail -$1|head -1|awk '/@sync-/ { split(\$1,snapshot,\"@\"); print substr(snapshot[2],6) }'"

	if [ "$4" == "" ]; then
		last=$(eval $cmd)
	else
		last=$(ssh root@$4 $cmd)
	fi
}

sync() {
	last_snapshot 1 $2 $3 $4
	last_remote=${last}
	echo "last remote snapshot: ${last_remote}"
	last_snapshot 1 $1 $3
	last_local=${last}
	echo "last local snapshot: ${last_local}"

	if [ "$last_remote" == "" ]; then
		echo "no remote snapshot: full send->receive"
		cmda="zfs send $1/$3@sync-${last_local}"
		cmdb="zfs receive -F -d $2"
		cmd="${cmda} | ssh root@$4 ${cmdb}"
		echo "running: $cmd"
		eval $cmd
		return
	fi

	echo "Syncing: from ${last_local} to ${last_remote}"

	cmda="zfs send -i $1/$3@sync-${last_remote} $1/$3@sync-${last_local}"
	cmdb="zfs receive -F -d $2"
	cmd="${cmda} | ssh root@$4 ${cmdb}"

	eval $cmd
}

cleanup() {
	last_snapshot 1 $2 $3 $4
	last_remote=${last}
	last_snapshot 1 $1 $3
	last_local=${last}

	echo "Start cleanup local: ${last_local}, remote: ${last_remote}"	

	if [ "${last_local}" != "${last_remote}" ]; then
		echo "Snapshots do not match after sync; local: ${last_local}, remote: ${last_remote}" >&2
		[ -f "$pid" ] && rm -f "$pid"
		exit 1
	fi

	for destroy in $(zfs list -H -r -s creation -t snapshot -o name $1/$3 | grep @sync- | grep -v $1/$3@sync-${last_local}); do
		echo "Destroying local: ${destroy}"
		zfs destroy ${destroy}
	done

	for destroy in $(ssh root@$4 zfs list -H -r -s creation -t snapshot -o name $2/$3 | grep @sync- | grep -v $2/$3@sync-${last_remote}); do
		echo "Destroying remote: ${destroy}"
		ssh root@$4 zfs destroy ${destroy}
	done
}

echo "Parameters"
echo "=========="
echo "Source: ${poolsource}"
echo "Dest:   ${pooldest}"
echo "FileS:  ${filesystem}"
echo "Interv: ${interval}"
echo "Remote: ${remote}"
echo "Snap:   ${curtime}"
echo

# Check
check

# Create snapshot
create_snapshot ${poolsource} ${filesystem} ${curtime}

# Sync incremental
sync ${poolsource} ${pooldest} ${filesystem} ${remote}

# Clean up local
cleanup ${poolsource} ${pooldest} ${filesystem} ${remote}

# remove pid
[ -f "$pid" ] && rm "$pid"
